/*
   translate.c - translate between different pixel formats
*/

/*
    OSXvnc Copyright (C) 2001 Dan McGuirk <mcguirk@incompleteness.net>.
    Original Xvnc code Copyright (C) 1999 AT&T Laboratories Cambridge.
    All Rights Reserved.

    This is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This software is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this software; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
    USA.
*/

#include <string.h>
#include <rfb/rfbproto.h>
#include <rfb/rfbregion.h>

static void PrintPixelFormat(               rfbPixelFormat * );
static rfbBool rfbSetClientColourMapBGR233( rfbClient      * );

rfbBool rfbEconomicTranslate = FALSE;

/*
   Some standard pixel formats.
*/

static const rfbPixelFormat BGR233Format =
{ 8, 8, 0, 1, 7, 7, 3, 0, 3, 6, 0, 0
};


/*
   Macro to compare pixel formats.
*/

#define PF_EQ(x,y)                                                      \
        ((x.bitsPerPixel == y.bitsPerPixel) &&                          \
         (x.depth == y.depth) &&                                        \
         ((x.bigEndian == y.bigEndian) || (x.bitsPerPixel == 8)) &&     \
         (x.trueColour == y.trueColour) &&                              \
         (!x.trueColour || ((x.redMax == y.redMax) &&                   \
                            (x.greenMax == y.greenMax) &&               \
                            (x.blueMax == y.blueMax) &&                 \
                            (x.redShift == y.redShift) &&               \
                            (x.greenShift == y.greenShift) &&           \
                            (x.blueShift == y.blueShift))))

#define CONCAT2(a,b) a##b
#define CONCAT2E(a,b) CONCAT2(a,b)
#define CONCAT3(a,b,c) a##b##c
#define CONCAT3E(a,b,c) CONCAT3(a,b,c)
#define CONCAT4(a,b,c,d) a##b##c##d
#define CONCAT4E(a,b,c,d) CONCAT4(a,b,c,d)

#undef OUT
#undef IN

#define OUT 8
#include "tableinitcmtemplate.c"
#include "tableinittctemplate.c"

#define IN 8
#include "tabletranstemplate.c"

#undef IN
#define IN 16
#include "tabletranstemplate.c"

#undef IN
#define IN 32
#include "tabletranstemplate.c"

#undef IN
#undef OUT
#define OUT 16
#include "tableinitcmtemplate.c"
#include "tableinittctemplate.c"

#define IN 8
#include "tabletranstemplate.c"
#undef IN
#define IN 16
#include "tabletranstemplate.c"
#undef IN
#define IN 32
#include "tabletranstemplate.c"
#undef IN
#undef OUT

#define OUT 32
#include "tableinitcmtemplate.c"
#include "tableinittctemplate.c"
#define IN 8
#include "tabletranstemplate.c"
#undef IN
#define IN 16
#include "tabletranstemplate.c"
#undef IN
#define IN 32
#include "tabletranstemplate.c"
#undef IN
#undef OUT

#ifdef ALLOW24BPP
#define COUNT_OFFSETS 4
#define BPP2OFFSET(bpp) ((bpp)/8-1)
#include "tableinit24.c"
#define BPP 8
#include "tabletrans24template.c"
#undef BPP
#define BPP 16
#include "tabletrans24template.c"
#undef BPP
#define BPP 24
#include "tabletrans24template.c"
#undef BPP
#define BPP 32
#include "tabletrans24template.c"
#undef BPP
#else
#define COUNT_OFFSETS 3
#define BPP2OFFSET(bpp) ((int)(bpp)/16)
#endif

typedef void (*rfbInitCMTableFnType)(char **table, rfbPixelFormat *in,
                                     rfbPixelFormat *out,rfbColourMap* cm);
typedef void (*rfbInitTableFnType)(char **table, rfbPixelFormat *in,
                                   rfbPixelFormat *out);

static rfbInitCMTableFnType rfbInitColourMapSingleTableFns[COUNT_OFFSETS] =
{ rfbInitColourMapSingleTable8,
  rfbInitColourMapSingleTable16,
#ifdef ALLOW24BPP
  rfbInitColourMapSingleTable24,
#endif
  rfbInitColourMapSingleTable32
};

static rfbInitTableFnType rfbInitTrueColourSingleTableFns[COUNT_OFFSETS] =
{ rfbInitTrueColourSingleTable8,
  rfbInitTrueColourSingleTable16,
#ifdef ALLOW24BPP
  rfbInitTrueColourSingleTable24,
#endif
  rfbInitTrueColourSingleTable32
};

static rfbInitTableFnType rfbInitTrueColourRGBTablesFns[COUNT_OFFSETS] =
{ rfbInitTrueColourRGBTables8,
  rfbInitTrueColourRGBTables16,
#ifdef ALLOW24BPP
  rfbInitTrueColourRGBTables24,
#endif
  rfbInitTrueColourRGBTables32
};

static rfbTranslateFnType rfbTranslateWithSingleTableFns[COUNT_OFFSETS][COUNT_OFFSETS] =
{ { rfbTranslateWithSingleTable8to8,
    rfbTranslateWithSingleTable8to16,
#ifdef ALLOW24BPP
    rfbTranslateWithSingleTable8to24,
#endif
    rfbTranslateWithSingleTable8to32
  },
  { rfbTranslateWithSingleTable16to8,
    rfbTranslateWithSingleTable16to16,
#ifdef ALLOW24BPP
    rfbTranslateWithSingleTable16to24,
#endif
    rfbTranslateWithSingleTable16to32
  },
#ifdef ALLOW24BPP
  { rfbTranslateWithSingleTable24to8,
    rfbTranslateWithSingleTable24to16,
    rfbTranslateWithSingleTable24to24,
    rfbTranslateWithSingleTable24to32
  },
#endif
  { rfbTranslateWithSingleTable32to8,
    rfbTranslateWithSingleTable32to16,
#ifdef ALLOW24BPP
    rfbTranslateWithSingleTable32to24,
#endif
    rfbTranslateWithSingleTable32to32
  }
};

static rfbTranslateFnType rfbTranslateWithRGBTablesFns[COUNT_OFFSETS][COUNT_OFFSETS] =
{ { rfbTranslateWithRGBTables8to8,
    rfbTranslateWithRGBTables8to16,
#ifdef ALLOW24BPP
    rfbTranslateWithRGBTables8to24,
#endif
    rfbTranslateWithRGBTables8to32
  },
  { rfbTranslateWithRGBTables16to8,
    rfbTranslateWithRGBTables16to16,
#ifdef ALLOW24BPP
    rfbTranslateWithRGBTables16to24,
#endif
    rfbTranslateWithRGBTables16to32
  },
#ifdef ALLOW24BPP
  { rfbTranslateWithRGBTables24to8,
    rfbTranslateWithRGBTables24to16,
    rfbTranslateWithRGBTables24to24,
    rfbTranslateWithRGBTables24to32
  },
#endif
  { rfbTranslateWithRGBTables32to8,
    rfbTranslateWithRGBTables32to16,
#ifdef ALLOW24BPP
    rfbTranslateWithRGBTables32to24,
#endif
    rfbTranslateWithRGBTables32to32
  }
};



/*
   rfbTranslateNone is used when no translation is required.
*/

void rfbTranslateNone( char *table
                       , rfbPixelFormat * in, rfbPixelFormat *out
                       , char           * iptr, char *optr
                       , int bytesBetweenInputLines
                       , int width, int height )
{ int bytesPerOutputLine = width * (out->bitsPerPixel / 8);

  while (height > 0)
  { memcpy(optr, iptr, bytesPerOutputLine);
    iptr += bytesBetweenInputLines;
    optr += bytesPerOutputLine;
    height--;
  }
}


/*
   rfbSetTranslateFunction sets the translation function.
*/

rfbBool rfbSetTranslateFunction( rfbClient * cl )
{ rfbLog("Pixel format for client %s:\n","cl->host");
  PrintPixelFormat(&cl->format);

  /*
     Check that bits per pixel values are valid
  */

  if ((cl->screen->serverFormat.bitsPerPixel != 8) &&
      (cl->screen->serverFormat.bitsPerPixel != 16) &&
#ifdef ALLOW24BPP
      (cl->screen->serverFormat.bitsPerPixel != 24) &&
#endif
      (cl->screen->serverFormat.bitsPerPixel != 32))
  { rfbErr("%s: server bits per pixel not 8, 16 or 32 (is %d)\n",
           "rfbSetTranslateFunction",
           cl->screen->serverFormat.bitsPerPixel);
    rfbCloseClient(cl);
    return FALSE;
  }

  if ((cl->format.bitsPerPixel != 8) &&
      (cl->format.bitsPerPixel != 16) &&
#ifdef ALLOW24BPP
      (cl->format.bitsPerPixel != 24) &&
#endif
      (cl->format.bitsPerPixel != 32))
  { rfbErr("%s: client bits per pixel not 8, 16 or 32\n",
           "rfbSetTranslateFunction");
    rfbCloseClient(cl);
    return FALSE;
  }

  if (!cl->format.trueColour && (cl->format.bitsPerPixel != 8))
  { rfbErr("rfbSetTranslateFunction: client has colour map "
           "but %d-bit - can only cope with 8-bit colour maps\n",
           cl->format.bitsPerPixel);
    rfbCloseClient(cl);
    return FALSE;
  }

  /*
     bpp is valid, now work out how to translate
  */

  if (!cl->format.trueColour)
  { /*
       truecolour -> colour map

       Set client's colour map to BGR233, then effectively it's
       truecolour as well
    */

    if (!rfbSetClientColourMapBGR233(cl))
      return FALSE;

    cl->format = BGR233Format;
  }

  /* truecolour -> truecolour */

  if (PF_EQ(cl->format,cl->screen->serverFormat))
  {

    /* client & server the same */

    rfbLog("no translation needed\n");
    cl->translateFn = rfbTranslateNone;
    return TRUE;
  }

  if ((cl->screen->serverFormat.bitsPerPixel < 16) ||
      ((!cl->screen->serverFormat.trueColour || !rfbEconomicTranslate) &&
       (cl->screen->serverFormat.bitsPerPixel == 16)))
  {

    /* we can use a single lookup table for <= 16 bpp */

    cl->translateFn = rfbTranslateWithSingleTableFns
                      [ BPP2OFFSET( cl->screen->serverFormat.bitsPerPixel)]
                      [ BPP2OFFSET( cl->format.bitsPerPixel              )];

    if(cl->screen->serverFormat.trueColour)
      (*rfbInitTrueColourSingleTableFns
       [BPP2OFFSET(cl->format.bitsPerPixel)]) (&cl->translateLookupTable,
           &(cl->screen->serverFormat), &cl->format);
    else
      (*rfbInitColourMapSingleTableFns
          [BPP2OFFSET(cl->format.bitsPerPixel)]) (&cl->translateLookupTable,
              &(cl->screen->serverFormat), &cl->format,&cl->screen->colourMap);

  }
  else
  {

    /* otherwise we use three separate tables for red, green and blue */

    cl->translateFn = rfbTranslateWithRGBTablesFns
                      [BPP2OFFSET(cl->screen->serverFormat.bitsPerPixel)]
                      [BPP2OFFSET(cl->format.bitsPerPixel)];

    (*rfbInitTrueColourRGBTablesFns
     [BPP2OFFSET(cl->format.bitsPerPixel)]) (&cl->translateLookupTable,
         &(cl->screen->serverFormat), &cl->format);
  }

  return TRUE;
}



/*
   rfbSetClientColourMapBGR233 sets the client's colour map so that it's
   just like an 8-bit BGR233 true colour client.
*/

static rfbBool rfbSetClientColourMapBGR233(rfbClient * cl)
{ char buf[sz_rfbSetColourMapEntriesMsg + 256 * 3 * 2];
  rfbSetColourMapEntriesMsg *scme = (rfbSetColourMapEntriesMsg *)buf;
  uint16_t *rgb = (uint16_t *)(&buf[sz_rfbSetColourMapEntriesMsg]);
  int i, len;
  int r, g, b;

  if (cl->format.bitsPerPixel != 8 )
  { rfbErr("%s: client not 8 bits per pixel\n",
           "rfbSetClientColourMapBGR233");
    rfbCloseClient(cl);
    return FALSE;
  }

  scme->type = rfbSetColourMapEntries;

  scme->firstColour = Swap16IfLE(0);
  scme->nColours = Swap16IfLE(256);

  len = sz_rfbSetColourMapEntriesMsg;

  i = 0;

  for (b = 0; b < 4; b++)
  { for (g = 0; g < 8; g++)
    { for (r = 0; r < 8; r++)
      { rgb[i++] = Swap16IfLE(r * 65535 / 7);
        rgb[i++] = Swap16IfLE(g * 65535 / 7);
        rgb[i++] = Swap16IfLE(b * 65535 / 3);
      }
    }
  }

  len += 256 * 3 * 2;

  if (rfbPushClientStream( cl,  buf, len) < 0)
  { rfbLogPerror("rfbSetClientColourMapBGR233: write");
    rfbCloseClient(cl);
    return FALSE;
  }
  return TRUE;
}

/* this function is not called very often, so it needn't be
   efficient. */

/*
   rfbSetClientColourMap is called to set the client's colour map.  If the
   client is a true colour client, we simply update our own translation table
   and mark the whole screen as having been modified.
*/

rfbBool rfbSetClientColourMap(rfbClient * cl, int firstColour, int nColours)
{ if (cl->screen->serverFormat.trueColour || !cl->readyForSetColourMapEntries)
  { return TRUE;
  }

  if (nColours == 0)
  { nColours = cl->screen->colourMap.count;
  }

  if (cl->format.trueColour)
  { (*rfbInitColourMapSingleTableFns
     [BPP2OFFSET(cl->format.bitsPerPixel)]) (&cl->translateLookupTable,
         &cl->screen->serverFormat, &cl->format,&cl->screen->colourMap);

    sraRgnDestroy(cl->modifiedRegion);
    cl->modifiedRegion =
      sraRgnCreateRect(0,0,cl->screen->width,cl->screen->height);

    return TRUE;
  }

  return rfbSendSetColourMapEntries(cl, firstColour, nColours);
}


/*
   rfbSetClientColourMaps sets the colour map for each RFB client.
*/

void
rfbSetClientColourMaps(rfbScreenInfoPtr rfbScreen, int firstColour, int nColours)
{ rfbClientIteratorPtr i;
  rfbClient * cl;

  i = rfbGetClientIterator(rfbScreen);
  while((cl = rfbClientIteratorNext(i)))
    rfbSetClientColourMap(cl, firstColour, nColours);
  rfbReleaseClientIterator(i);
}

static void PrintPixelFormat(rfbPixelFormat *pf)
{ if (pf->bitsPerPixel == 1)
  { rfbLog("  1 bpp, %s sig bit in each byte is leftmost on the screen.\n",
           (pf->bigEndian ? "most" : "least"));
  }
  else
  { rfbLog("  %d bpp, depth %d%s\n",pf->bitsPerPixel,pf->depth,
           ((pf->bitsPerPixel == 8) ? ""
            : (pf->bigEndian ? ", big endian" : ", little endian")));
    if (pf->trueColour)
    { rfbLog("  true colour: max r %d g %d b %d, shift r %d g %d b %d\n",
             pf->redMax, pf->greenMax, pf->blueMax,
             pf->redShift, pf->greenShift, pf->blueShift);
    }
    else
    { rfbLog("  uses a colour map (not true colour).\n");
    }
  }
}
